/*-------------------------------------------------------------------------
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#include <cc_compat.h>
#include <stdio.h>
#include "ds2756.h"
#include "onewirebatt.h"
#include "lifebatt.h"
#include "io.h"
#include "power.h"
#include "ac.h"
#include "timer.h"
#include "mppt.h"
#include "sdi.h"
#include "event.h"
#include "battery.h"
#include "charger.h"


#define LIFE_SHOW_STATES	1

unsigned char LIFE_FLAGS;
#define need_LDACR_update   (1<<0)      // 1:

//0xFBD1
#define Initialization         0
// unused #define BatteryHandling        1
// unused #define BatteryHandlingWithAC  2
//0xFBD0
#define PrepareBatteryCharging	1
#define BatteryInCharging		2
// unused #define NoBatteryToCharging		3
#define BatteryFull				4
#define CFC_Idle				5
#define WaitMpptOk				6
#define CFC_Overtemp			7
#define CFC_Error				8

#define LIFE_INIT					0
// unused #define LIFE_DISABLE		1
// unused #define LIFE_WAIT_ENABLE	2
#define LIFE_BATTERY_INIT			3
#define LIFE_CHECK_VOLTAGE			4
#define LIFE_CHECK_EXT_POWER		5

#define LIFE_EXTERNAL_POWER_IN		7
#define LIFE_EXTERNAL_POWER			8
#define LIFE_EXTERNAL_POWER_OUT		9
#define LIFE_NO_EXTERNAL_POWER		10
#define LIFE_START_TRICKLE			11
#define LIFE_TRICKLE_CHARGE			12
#define LIFE_ACR_SYNCUP				13

/*------------------------ battery parameter -------------------------------*/

// If you measure the voltage on the battery terminals it seems to be off
// by up to 0.1 V.  I suspect this may be from the fact that the sensor reads
// the voltage directly at the battery and the terminals have a mosfets
// between the cells and the terminals.
// It may also just be the tolerance on the sensor.
// TODO: look at this on a bunch of different batteries and
// see how much it floats around.

#define cBAT_TEMPERATURE58C		0x3A00      	// 58 degree
#define cBAT_TEMPERATURE54C		0x3600      	// 54 degree
#define cBAT_TEMPERATURE50C		0x3200      	// 50 degree
#define cBAT_TEMPERATURE45C		0x2D00			// 45 degree

#define LIFE_TEMP_OVERTEMP		degc_to_guage(55)
#define LIFE_TEMP_RESTART		degc_to_guage(54)


// the millivolt numbers were chosen to recreate the hex values
// that used to be here, though they've been rounded in the last
// digit.  they could probably be rounded further.  (the comments
// didn't match the true values before, either.)
#define cBAT_VOLT785			mv_to_gauge(7850)		// 7.80 V
#define cBAT_VOLT570			mv_to_gauge(5750)		// 5.75, 2.87 * 2, low voltage
#define cBAT_VOLT_SHUTOFF		mv_to_gauge(4950)		// Point at which a hard power off happens
#define cBAT_VOLT_CRIT			mv_to_gauge(5700)		// Critical voltage warning marker.
#define cBAT_VOLT_CLR_FULL		mv_to_gauge(6010)		// Voltage setting for incorrect full flag checking
#define cBAT_VOLT_LIFE_TRICKLE		mv_to_gauge(5400)		// Level where we turn on trickle for LiFe
#define LIFE_VOLT_CRIT_HYSTERESIS	mv_to_gauge(500)		// Critical voltage warning marker.

#define BAT_CV_VOLTAGE			mv_to_gauge(7300)
#define BAT_TAPER_CURRENT		ma_to_guage_Ib(120)		// Charging Ib where we consider battery full

#define LFP_SOC_WARNING     10

char code cBAT_ACR1p[] =   { 71, 67 };
char code cBAT_ACR1pCC[] = { 75, 73 };
char code cBAT_ACR1pCV[] = { 72, 80 };

unsigned int batt_good_timer;
unsigned char xdata batt_is_GP;
unsigned char xdata life_chg_state_prev;
unsigned char xdata life_chg_flow_ctrl_prev;

signed int xdata real_acr;
signed int xdata last_dist_acr;

unsigned char xdata ChargeFlowControl;
unsigned char xdata ProcessBatteryCharge;
unsigned char xdata bBAT_OVT_CNT;
unsigned char xdata bBAT_FULL_CNT;
unsigned char xdata bBAT_OVP_CNT;
unsigned char xdata bBAT_CV_CNT;
unsigned char xdata bBAT_LOW_CNT;
unsigned char xdata life_chg_state;
//signed  int   wBAT_ACR;
unsigned char xdata BATT_BUS_WDT;
unsigned char xdata LAST_DS_VOLT_LSB;
unsigned char xdata LAST_DS_ACR_LSB;

long xdata no_vchange_timer;

#ifdef LATER
#define cfc_status(n) EC_RAM[0xE0] = (n);
#else
#define cfc_status(n)
#endif

/*-------------------- Variables -------------------------------------------*/

unsigned char xdata NewSOC;
unsigned char xdata NewSOC_r;
unsigned char xdata Display_Debug_Message;

extern unsigned char xdata bEEPROM_WRITE_ALL;
extern unsigned char xdata BATT_TEMP_REAL_ACR[2];
extern unsigned char xdata bOneWireBattery;

extern void UpdateBatteryInformation(void);
extern unsigned char BatteryOVPcheck(void);
extern void CheckBatteryFull(void);
extern void HandleBatteryDischarging(void);
extern void UpdateGaugeLDACR(void);
extern void MeasureBatterySOC(void);
extern unsigned char watch_for_no_v_change(void);

static void charge_flow_control(void);
static void life_charge_on(void);
static void life_trickle_on(void);
static void life_trickle_off(void);
static void life_sync_acr(void);

#define V_fmt(Vg) (((signed long)Vg)*488/1600)

void batt_debug_dump(void)
{

	signed int  rACR;
	signed int  ldACR;

	rACR = GAUGE_CACHE[DS_ACR_MSB];
	rACR = (rACR  << 8) + GAUGE_CACHE[DS_ACR_LSB];

	print_tstamp();
	printf(" Vb: %ld  I: %ld  W: %4d  SOC:%02d ACR:%d PL:%x L:%d sleep: %d\n",
		   Vb_inst_in_mV, Ib_inst_in_mA, Pb_inst_in_mW,
		   (unsigned int) SOC,
		   rACR,
		   (unsigned int)mppt_get_limit(),
		   (unsigned int)read_PWR_LMT_ON(),
		   (unsigned int)read_SOC_SLEEP()
		  );

	// If you just want V, I, and W set the short bit.
	if ((battery_debug_flags & BATTERY_DEBUG_SHORT) != 0) return;

	printf("   AC=%d bin=%d CHG=%x WakeC=%d WakeUP=%d  ",
		   (unsigned int) is_ac_adapter_in(),
		   (unsigned int) !!(sMBAT_STATUS & bat_in),
		   (unsigned int) !!(sMCHARGE_STATUS & bat_charge),
		   (unsigned int) !!(sMCHARGE_STATUS & bat_WakeChg),
		   (unsigned int) !!(sMCHARGE_STATUS & bat_wakeup)
		  );
	printf("\n");

	printf("   Owb=%d bf=%d CHGS:%02x rACR:%d ldACR:%d Tb:%04x\n",
		   (unsigned int) bOneWireBattery,
		   (unsigned int) !!(sMBAT_STATUS & bat_full),
		   (unsigned int) sMCHARGE_STATUS, rACR,
		   ldACR, wBAT_TEMPERATURE
		  );

	/*  printf("   BMISC:%02x eBMISC:%02x BSTAT:%02x Vbx_avg:%04x pwrlim %d\n",
		   (unsigned int) bBAT_MISCFlag,
		   (unsigned int) GAUGE_CACHE[DS_BAT_MISC_FLAG],
		   (unsigned int) sMBAT_STATUS,
		   (unsigned int) wBAT_VOLTAGE,
		   (unsigned int) read_pwr_limit()
		  ); */
}

void life_battery_init(void)
{
	sMBAT_STATUS &= ~bat_error;
	sMBAT_STATUS &= ~bat_low;
	sMBAT_STATUS &= ~bat_full;
	bBAT_CV_CNT = 0;
	ChargeFlowControl = Initialization;
	life_charge_off();
	life_trickle_off();;

	SOC = GAUGE_CACHE[DS_LAST_DISC_SOC];

	real_acr  		= GET_REAL_ACR;
	last_dist_acr	= GET_LAST_DIST_ACR;

	bBAT_MISCFlag = GAUGE_CACHE[DS_BAT_MISC_FLAG];
	if ((bBAT_MISCFlag & FULLY_CHARGED) != 0)
		sMBAT_STATUS |= bat_full;
	else
		sMBAT_STATUS &= ~bat_full;

	no_vchange_timer = get_ms_timer_ext();
}

void life_init_module(void)
{
	life_battery_init();
	life_chg_state = LIFE_INIT;
	life_chg_state_prev = 1;
	life_chg_flow_ctrl_prev = 1;
	LIFE_FLAGS &= ~need_LDACR_update;
}

void lifepo4_process(unsigned char sub_type)
{
	// TODO: Eventually we may have to deal with another chemistry
	// but not right now.
	batt_is_GP = sub_type;

	// We have learned that ESD will corrupt the shadow ram
	// of the guage.
	if ((GAUGE_CACHE[DS_PACK_INFO] != 0x6A) )
	{
		// Rest the critical value and halt cause everything is unsafe
		GAUGE_CACHE[DS_PACK_INFO] = 0x6A;
		bEEPROM_WRITE_ALL = 0;
		ChargeFlowControl = CFC_Error;
		SetAbnormalCauseCode( BATT_ERR_PACK_INFO );
	}

	if ( BatteryOVPcheck() || watch_for_no_v_change())
	{
		/* battery abnormal */
		ChargeFlowControl = CFC_Error;
		sMBAT_STATUS |= bat_error;
	}

#if LIFE_SHOW_STATES
	if ((life_chg_state_prev != life_chg_state) ||
			(life_chg_flow_ctrl_prev != ChargeFlowControl)
	   )
	{
		life_chg_state_prev = life_chg_state;
		life_chg_flow_ctrl_prev	= ChargeFlowControl;
		print_tstamp();
		printf("  LCS:%d CFC:%d CHG=%x Vb:%ld Vavg:0x%04x\n",
			   (unsigned int)life_chg_state,
			   (unsigned int)ChargeFlowControl,
			   (unsigned int) !!(sMCHARGE_STATUS & bat_charge),
			   Vb_inst_in_mV,
			   (unsigned int) wBAT_VOLTAGE
			  );
	}
#endif

	switch (life_chg_state)
	{
		case LIFE_INIT:
			life_chg_state = LIFE_BATTERY_INIT;
			break;

		case LIFE_BATTERY_INIT:
			life_battery_init();
			life_chg_state = LIFE_ACR_SYNCUP;
			break;

		case LIFE_ACR_SYNCUP:
			life_sync_acr();
			life_chg_state = LIFE_CHECK_VOLTAGE;
			break;

		case LIFE_CHECK_VOLTAGE:
			// Are we above the trickle charge limit?
			if (ISL_is_tricklecharging())
			{
				life_chg_state = LIFE_START_TRICKLE;
				puts("ISL Trickle charging");
				break;
			}
			else if (wBAT_VOLTAGE < cBAT_VOLT_LIFE_TRICKLE)
			{
				life_chg_state = LIFE_START_TRICKLE;
				break;
			}
			life_chg_state = LIFE_CHECK_EXT_POWER;
			break;

		case LIFE_CHECK_EXT_POWER:
			// At this stage we might have a battery that will do normal
			// operation.

			if (is_ac_adapter_in())
			{
				batt_good_timer = get_ms_timer();;
				life_chg_state = LIFE_EXTERNAL_POWER_IN;
			}
			else
			{   // there's no adapter, just decide the battery is good
				battery_info |= BATTERY_GOOD_FLAG;
				life_chg_state = LIFE_EXTERNAL_POWER_OUT;
			}
			break;

		case LIFE_EXTERNAL_POWER_IN:
			// it can take some time for onewire to detect a battery removal.
			// we come here indirectly from trickle charging --
			// don't declare the battery good to use until some time has gone
			// by.  (onewire currently cycles at 100ms.)
			if (!check_timer(batt_good_timer, 125))
				break;

			battery_info |= BATTERY_GOOD_FLAG;
			life_chg_state = LIFE_EXTERNAL_POWER;
			break;

		case LIFE_EXTERNAL_POWER:

			if (!is_ac_adapter_in())
			{
				life_chg_state = LIFE_EXTERNAL_POWER_OUT;
			}

			MeasureBatterySOC();
			charge_flow_control();
			break;

		case LIFE_EXTERNAL_POWER_OUT:
			life_charge_off();
			life_trickle_off();
			battery_set_leds_state(LEDS_STATE_DISCHARGE);
			life_chg_state = LIFE_NO_EXTERNAL_POWER;
			break;

		case LIFE_NO_EXTERNAL_POWER:

			if (is_ac_adapter_in())
			{
				batt_good_timer = get_ms_timer();;
				life_chg_state = LIFE_EXTERNAL_POWER_IN;
			}

			MeasureBatterySOC();
			ChargeFlowControl = Initialization;
			HandleBatteryDischarging();
			break;

		case LIFE_START_TRICKLE:
			battery_info &= ~BATTERY_GOOD_FLAG;
			sMBAT_STATUS |= bat_low;
			// A battery in trickle is very low
			SOC = 0;
			print_tstamp();
			puts("Starting trickle charge");
			life_trickle_on();
			life_charge_off();
			life_chg_state = LIFE_TRICKLE_CHARGE;
			break;

		case LIFE_TRICKLE_CHARGE:
			battery_set_leds_state(LEDS_STATE_TRICKLE);

			if (!is_ac_adapter_in())
			{
				life_chg_state = LIFE_EXTERNAL_POWER_OUT;
			}

			if ((!ISL_is_tricklecharging()) ||
					(wBAT_VOLTAGE > cBAT_VOLT_LIFE_TRICKLE))
			{
				print_tstamp();
				puts("Trickle charge complete");
				life_trickle_off();
				SOC = 3;
				UpdateGaugeLDACR();
				life_chg_state = LIFE_CHECK_EXT_POWER;
			}
			break;
	}
}

// TODO: This function still needs some clean up from the refactor
// Some of the flags here may be better set elsewhere.
static void charge_flow_control(void)
{
	switch (ChargeFlowControl)
	{
		case CFC_Idle:
			cfc_status(0xe9);
			break;

			/* handle battery charge */
		case Initialization:
		{

			if ( ((sMBAT_STATUS & bat_full) == 0) )
			{
				ChargeFlowControl = WaitMpptOk;
				cfc_status(0xe4);
			}
			else
			{
				// This attempts to detect an error condtion where something external
				// self-discharge, etc, drains a full battery below the level at which the
				// EC will run.  So the EC in discharge never gets a chance to mark it as non-full
				// thus it never charges.
				// This also can happen with the charge balance problems.
				if ( wBAT_VOLTAGE < cBAT_VOLT_CLR_FULL  )
				{
					printf("bogus full flag\n");
					GAUGE_CACHE[DS_BAT_MISC_FLAG] &= ~FULLY_CHARGED;
					bBAT_MISCFlag = GAUGE_CACHE[DS_BAT_MISC_FLAG];
					sMBAT_STATUS &= ~bat_full;
					bHDQ_ACCESS_FLAG |= BATT_EEPROM0_UPDATE;
					cfc_status(0xe3);
				}
				ChargeFlowControl = BatteryFull;
			}

			if ((sMBAT_STATUS & bat_error) != 0)
			{
				ChargeFlowControl = CFC_Error;
				cfc_status(0xe5);
			}

			if ((bBAT_MISCFlag & LOW_VOLT) != 0)
			{
				// write data to gauge
				printf("Clr LOW_VOLT\n");
				bBAT_MISCFlag &= ~LOW_VOLT;

				GAUGE_CACHE[DS_BAT_MISC_FLAG] &= 0xFE;  // clear low voltage of gauge
				bHDQ_ACCESS_FLAG |= BATT_EEPROM0_UPDATE;
				cfc_status(0xe6);
			}
			// +++ DAVID, 2007/4/12
			sMBAT_STATUS &= ~bat_low;
			// --- DAVID
		}
		break;

		case WaitMpptOk:
			cfc_status(0xea);
			if ((mppt_flags & mppt_is_chg_ok) != 0)
			{
				ChargeFlowControl = PrepareBatteryCharging;
			}
			break;

		case PrepareBatteryCharging:		// 1

			ChargeFlowControl = BatteryInCharging;
			life_charge_on();
			life_trickle_off();
			cfc_status(0xeb);
			break;

		case BatteryInCharging:				// 2
			battery_set_leds_state(LEDS_STATE_CHARGE);

			if ((signed int)wBAT_TEMPERATURE > LIFE_TEMP_OVERTEMP)
			{
				/* charging temperature checking */
				printf("cfc OverTemp\n");
				ChargeFlowControl = CFC_Overtemp;
				sMCHARGE_STATUS |= bat_overtemp;
				life_charge_off();
				life_trickle_off();;
				cfc_status(0xe7);
				break;
			}

			ISL_charger_keepalive();

			if ((mppt_flags & mppt_is_chg_ok) != 0)
			{
				sMCHARGE_STATUS &= ~bat_overtemp;
				CheckBatteryFull();
			}
			else
			{
				life_charge_off();
				life_trickle_off();
				battery_set_leds_state(LEDS_STATE_DISCHARGE);
				ChargeFlowControl = WaitMpptOk;
			}
			break;

		case BatteryFull:
			printf("Bat Full\n");
			life_charge_off();
			life_trickle_off();
			battery_set_leds_state(LEDS_STATE_FULL);
			ChargeFlowControl = CFC_Idle;
			break;

		case CFC_Error:						// 8
			life_charge_off();
			life_trickle_off();
			ChargeFlowControl = CFC_Idle;
			battery_set_leds_state(LEDS_STATE_ERROR);
			break;

		case CFC_Overtemp:
			// Overtemp is a soft error.  Restart charging when the
			// temp drops back down.
			battery_set_leds_state(LEDS_STATE_OVERTEMP);

			if ((signed int)wBAT_TEMPERATURE < LIFE_TEMP_RESTART )
			{
				ChargeFlowControl = BatteryInCharging;
				sMCHARGE_STATUS &= ~bat_overtemp;
				life_charge_on();
				life_trickle_off();
			}
			break;

		default:
			break;
	}
}

void lifepo4_reset(void)
{
	sMBAT_STATUS &= ~bat_full;
	battery_info &= ~BATTERY_GOOD_FLAG;
	life_charge_off();
	life_trickle_off();
	life_chg_state = LIFE_INIT;
	life_chg_state_prev = 0xff;
	ProcessBatteryCharge = Initialization;
	ChargeFlowControl = Initialization;
	battery_set_leds_state(LEDS_STATE_REMOVED);
}

/*-----------------------------------------------------------------------------
 Name: BatteryOVPcheck
-----------------------------------------------------------------------------*/
unsigned char BatteryOVPcheck(void)
{
	/* check battery over voltage protection */
	// if (wBAT_VOLTAGE > cBAT_VOLT775) { /* over voltage checking */
	// +++ DAVID, 2007/4/3
	if ( wBAT_VOLTAGE > cBAT_VOLT785 )
		// --- DAVID
	{
		if ( ((++bBAT_OVP_CNT) > 5) && (sMBAT_STATUS & bat_error) == 0)
		{
			/* return cause code */
			bBAT_OVP_CNT = 5;
			SetAbnormalCauseCode ( BATT_ERR_LIFE_OVER_V ) ;
			return 1;
		}
	}
	else
	{
		/* normal voltage */
		bBAT_OVP_CNT = 0;
	}
	// +++ DAVID +++
	return 0;
}

//----------------------------------------------------------------------------//
// CheckBatteryFull()                                                         //
//----------------------------------------------------------------------------//
void CheckBatteryFull(void)
{
	/* check battery full during normal charging */
	if (wBAT_VOLTAGE > BAT_CV_VOLTAGE)
	{
		cfc_status(0xe8);

		/* reached CV point */
		bBAT_CV_CNT++;
		if (bBAT_CV_CNT > 30)
		{
			/* CV checking */
			bBAT_CV_CNT = 31;
			if (SOC < 95)
			{
				/* at CV state the battery is almost full */
				SOC = 95;
				UpdateGaugeLDACR();
			}
			else
				if (wBAT_CURRENT < BAT_TAPER_CURRENT)
				{
					/* reached taper current */
					if ((++bBAT_FULL_CNT) > 30)
					{
						/* battery in full state */

						// TODO: See if you can merge these flag bytes
						GAUGE_CACHE[DS_BAT_MISC_FLAG] |= FULLY_CHARGED; // set full flag to gauge
						bBAT_MISCFlag = GAUGE_CACHE[DS_BAT_MISC_FLAG];
						if ((bBAT_MISCFlag & FULLY_CHARGED) != 0)
							sMBAT_STATUS |= bat_full;
						else
							sMBAT_STATUS &= ~bat_full;

						bHDQ_ACCESS_FLAG |= BATT_EEPROM0_UPDATE;
						ChargeFlowControl = BatteryFull;
						bBAT_FULL_CNT = 0;
						SOC = 100;
						UpdateGaugeLDACR();
					}
				}
		}
	}
	else
	{
		bBAT_FULL_CNT = 0;
		bBAT_CV_CNT = 0;
	}
}
//-------------------------------------------------------------------------
void HandleBatteryDischarging(void)
{

	if ( ((sMBAT_STATUS & bat_full) != 0) && ( SOC < 93 ) )
	{
		cfc_status(0xec);
		GAUGE_CACHE[DS_BAT_MISC_FLAG] &= ~FULLY_CHARGED;
		bBAT_MISCFlag = GAUGE_CACHE[DS_BAT_MISC_FLAG];
		if ((bBAT_MISCFlag & FULLY_CHARGED) != 0)
		{
			sMBAT_STATUS |= bat_full;
			battery_set_leds_state(LEDS_STATE_FULL);
		}
		else
			sMBAT_STATUS &= ~bat_full;

		bHDQ_ACCESS_FLAG |= BATT_EEPROM0_UPDATE;
	}

	if ( SOC < LFP_SOC_WARNING )
	{
		if ( (sMBAT_STATUS & bat_low) == 0 )
		{
			cfc_status(0xed);
			sMBAT_STATUS |= bat_low;
			UpdateGaugeLDACR();
			battery_set_leds_state(LEDS_STATE_LOW);
		}
	}
	else
	{
		sMBAT_STATUS &= ~bat_low;
	}

	if ( (wBAT_VOLTAGE < cBAT_VOLT_CRIT) && ((sMBAT_STATUS & bat_critical) == 0)  )
	{
		cfc_status(0xee);
		sMBAT_STATUS |= bat_critical;
		event_send_event(EVENT_BATTERY_CRITICAL);
		battery_set_leds_state(LEDS_STATE_CRITICAL);
		if ( (sMBAT_STATUS & bat_low) == 0 )
		{
			sMBAT_STATUS |= bat_low;
			UpdateGaugeLDACR();
		}

	}

	if (wBAT_VOLTAGE > cBAT_VOLT_CRIT+LIFE_VOLT_CRIT_HYSTERESIS)
	{
		sMBAT_STATUS &= ~bat_critical;
	}

	if ( wBAT_VOLTAGE < cBAT_VOLT_SHUTOFF )
	{
		// We should never be in runin and reach this condition
		// This means runin hung or crashed somehow.
		// disable the discharge so we don't power off and lose and evidence
		// of what caused runin to stop incorrectly.
		if (battery_is_in_runin())
		{
			print_tstamp();
			puts("Disabling runin discharge due to batt critical");
			battery_cmd_runin_discharge_disable();
		}

		/* monitor battery low threshold */
		if ((++bBAT_LOW_CNT) > 6)
		{
			bBAT_LOW_CNT = 0;
			/* count */
			if ((bBAT_MISCFlag & LOW_VOLT) == 0 && (sMBAT_STATUS & bat_low) == 0)
			{
				// write data to gauge
				bBAT_MISCFlag |= LOW_VOLT;
				GAUGE_CACHE[DS_BAT_MISC_FLAG] |= 0x01; // set low voltage to gauge
				bHDQ_ACCESS_FLAG |= BATT_EEPROM0_UPDATE;
			}

			sMBAT_STATUS |= bat_low;

			if (SOC > 3)
			{
				SOC = 3;
				UpdateGaugeLDACR();
			}
			if (power_host_is_on())
			{
				puts("Bat exhausted");
				power_command_shutdown();
			}
		}
	}
	else
	{
		bBAT_LOW_CNT = 0;
	}
}

/*
 * This routine tracks the changes in the ACR register and if it changes by
 * more than 1% SOC then it adjusts the SOC variable and takes a new copy.
 * Then it requesst that the the one wire subsystem update the copy in the
 * battery shadow ram and ultimately into the EEPROM.
 *
 * FIXME:
 * There is a complication.  The ACR register in the DS2756 does not wrap.
 * Instead if it reaches one of the extremes then it just stops until the
 * direction of power from the battery makes it count the other way.  This
 * means that if you reach one of the extremes (ie 0x7fff or 0x1000 ) and
 * the power is continuing to try and make the counter count then the
 * incremental counting used in this routine hangs.
 *
 * The workaround for now is to watch the guage value and if it ever
 * reaches the max or min values then wrap the counter to the other
 * extreme.  Then instruct the onewire subsystem to write that back to the
 * guage (the register is writable)
 *
 * Some thought should be spent on all this to see if there is a better way
 * to do this.
 *
*/
void MeasureBatterySOC(void)
{
	/* 1% is 85 of ACR value */
	signed int bat_acr_inc;
	signed int test_acr;

	static unsigned char lastSOC;

	// If a one wire opreation is in progress then
	// wait until its completed
	if (onewire_operation_pending()) return;

	real_acr = GET_REAL_ACR;

	if ( sMCHARGE_STATUS & (bat_charge | bat_WakeChg) )
	{
		bat_acr_inc = cBAT_ACR1pCC[batt_is_GP];              // default 90% efficiency

		// At 7.4V we switch to CV mode.
		if ( (wBAT_VOLTAGE > BAT_CV_VOLTAGE) )
		{
			bat_acr_inc = cBAT_ACR1pCV[batt_is_GP];         // CV mode 80% efficiency
		}
	}
	else
	{
		bat_acr_inc = cBAT_ACR1p[batt_is_GP];				// Discharge efficiency
	}

#if 0
	printf("MSOC: ra=%d, lda=%d, lda+=%d, lda-=%d SOC=%u\n",real_acr,last_real_acr,
		   (last_real_acr + bat_acr_inc),(last_real_acr - cBAT_ACR1p[batt_is_GP]),
		   (unsigned int)SOC
		  );
#endif

	// Deal with the fact that the ACR in the DS2756 guage does not
	// wrap. It stops at the extremes.  If we hit the ends then force it to wrap using the
	// extremes as magic values and requst an update
	if (real_acr == 32767)
	{
		real_acr = -32766;
		BATT_TEMP_REAL_ACR[0] = (unsigned char)(real_acr >> 8);
		BATT_TEMP_REAL_ACR[1] = (unsigned char)(real_acr & 0x00FF);
		bHDQ_ACCESS_FLAG1 |= BATT_REAL_ACR_UPDATE;
	}
	if (real_acr == -32768)
	{
		real_acr = 32766;
		BATT_TEMP_REAL_ACR[0] = (unsigned char)(real_acr >> 8);
		BATT_TEMP_REAL_ACR[1] = (unsigned char)(real_acr & 0x00FF);
		bHDQ_ACCESS_FLAG1 |= BATT_REAL_ACR_UPDATE;
	}

	test_acr = real_acr - last_dist_acr;

	if (test_acr > bat_acr_inc)
	{
		last_dist_acr += bat_acr_inc;
		if (SOC < 100) SOC++;
		UpdateGaugeLDACR();
	}

	if (test_acr < -bat_acr_inc)
	{
		last_dist_acr -= bat_acr_inc;
		if (SOC > 0) SOC--;
		UpdateGaugeLDACR();
	}

//	TODO: Re-implement the charge and discharge counts
//	that have been dropped.

#if 0
	test_acr = last_real_acr + bat_acr_inc;

	if (real_acr > test_acr )
	{
//		puts("  inc");
		last_real_acr = test_acr;

		if (SOC < 100) SOC = SOC + 1;

		UpdateGaugeACR();
		GAUGE_CACHE[DS_BAT_CHARGE_SOC]++;
		if (GAUGE_CACHE[DS_BAT_CHARGE_SOC] > 99)
		{
			/* cycle count per 100% */
			GAUGE_CACHE[DS_BAT_CHARGE_SOC] = 0;
			GAUGE_CACHE[DS_BAT_CHARGE_LSB]++;
			if (GAUGE_CACHE[DS_BAT_CHARGE_LSB] > 127)
			{
				/* bit7 don't care due to DS2756 issue */
				GAUGE_CACHE[DS_BAT_CHARGE_LSB] = 0;
				GAUGE_CACHE[DS_BAT_CHARGE_MSB]++;
			}
		}
	}

	bat_acr_inc = cBAT_ACR1p[batt_is_GP];					// Discharge efficiency
	test_acr = last_real_acr - bat_acr_inc;

	if (real_acr < test_acr)
	{
//		puts("  dec");
		last_real_acr = test_acr;

		if (SOC > 0) SOC = SOC - 1;

		UpdateGaugeACR();
		GAUGE_CACHE[DS_BAT_DISCHARGE_SOC]++;
		if (GAUGE_CACHE[DS_BAT_DISCHARGE_SOC] > 99)
		{
			/* cycle count per 100% */
			GAUGE_CACHE[DS_BAT_DISCHARGE_SOC] = 0;
			GAUGE_CACHE[DS_BAT_DISCHARGE_LSB]++;
			if (GAUGE_CACHE[DS_BAT_DISCHARGE_LSB] > 127)
			{
				/* bit7 don't care due to DS2756 issue */
				GAUGE_CACHE[DS_BAT_DISCHARGE_LSB] = 0;
				GAUGE_CACHE[DS_BAT_DISCHARGE_MSB]++;
			}
		}
	}
#endif

	if ( ((LIFE_FLAGS & need_LDACR_update) != 0) )
	{
		mppt_nice_log();
		print_tstamp();
		printf("LDACR Update request ACR:%d LDACR:%d\n",real_acr,last_dist_acr);
		LIFE_FLAGS &= ~need_LDACR_update;
		bHDQ_ACCESS_FLAG1 |= BATT_LAST_ACR_UPDATE;
	}

	if (lastSOC != SOC)
	{
		event_send_event(EVENT_BATTERY_SOC_CHANGE);
		lastSOC = SOC;
	}
}

void UpdateGaugeLDACR(void)
{
	GAUGE_CACHE[DS_LAST_DISC_SOC]		= SOC;
	GAUGE_CACHE[DS_LAST_DISC_ACR_MSB]	= (unsigned char)(last_dist_acr >> 8);
	GAUGE_CACHE[DS_LAST_DISC_ACR_LSB]	= (unsigned char)(last_dist_acr & 0x00FF);
	LIFE_FLAGS |= need_LDACR_update;
}

unsigned char watch_for_no_v_change(void)
{
	/* when the battery is in charging,
	   the voltage should be different */
	if ( (sMCHARGE_STATUS & bat_charge) != 0 )
	{
		if ( (GAUGE_CACHE[DS_VOLT_LSB] != LAST_DS_VOLT_LSB ) || (GAUGE_CACHE[DS_ACR_LSB] != LAST_DS_ACR_LSB) )
		{
			/* voltage changed in time */
			LAST_DS_VOLT_LSB = GAUGE_CACHE[DS_VOLT_LSB];
			LAST_DS_ACR_LSB  = GAUGE_CACHE[DS_ACR_LSB];

			no_vchange_timer = get_ms_timer_ext();
		}

		// If we haven't changed in 4 minutes the error
		if (check_timer_ext(no_vchange_timer,240000))
		{
			sMBAT_STATUS |= bat_error;
			puts("No Vchange");
			SetAbnormalCauseCode ( BATT_ERR_LIFE_NO_VCHANGE ) ;
			life_charge_off();
			life_trickle_off();
			return 1;
		}
	}
	else
	{
		no_vchange_timer = get_ms_timer_ext();
	}

	return 0;
}

void life_charge_off(void)
{
	mppt_nice_log();
	print_tstamp();
	puts("Chg off");
	ISL_charger_onoff(0);
	sMCHARGE_STATUS &= ~bat_charge;
}

static void life_charge_on(void)
{
	print_tstamp();
	puts("Chg On");
	ISL_charger_onoff(1);
	sMCHARGE_STATUS |= bat_charge;
}

static void life_trickle_on(void)
{
	puts("Trickle On");
	sMCHARGE_STATUS |= bat_WakeChg;
}

static void life_trickle_off(void)
{
	puts("Trickle Off");
	sMCHARGE_STATUS &= ~bat_WakeChg;
}


int charger_adc_to_mA(int adc)
{
	/*
	 * Sense resistor is .040 ohm and the chip output has a
	 * gain of 20.  ((3.3V/20)/0.040)/255 = .0161765 A/count
	 * or 161765 / 10000 mA/count.  converting to a power-of-2 divide
	 * gives identical results:
	 */
	return 4141L * (long)adc / 256;
}

void apply_power_limit(void)
{
	switch (board_rev)
	{
		case 0x0a3:
			ISL_set_power_limit_v1(mppt_get_limit());
			break;

		default:
			set_LV_SET(mppt_get_limit());
			break;
	}
}

void apply_max_power_limit(void)
{
	ISL_set_input_current_full();
}

/*
Compare the last copy of the ACR register with the copy that we have stored in
the battery gas guage.  If they differ then reconcile those differences by rolling
the SOC up or down to match what has happened to the battery.  This can happen
if the battery is charged or discharged outside the XO.
*/
static void life_sync_acr(void)
{
	signed int acr_diff;
	signed int soc_diff;
	signed int isoc;
	unsigned char bat_acr_inc;

	isoc = SOC;

	acr_diff = real_acr - last_dist_acr;

	if ( acr_diff >= 0 )
	{
		// Use the constant current charge effeciency value for this case.
		bat_acr_inc = cBAT_ACR1pCC[batt_is_GP];
	}
	else
	{
		bat_acr_inc = cBAT_ACR1p[batt_is_GP];
	}

	soc_diff = acr_diff / bat_acr_inc;

	isoc = SOC + soc_diff;

	if (isoc > 100) isoc = 100;
	if (isoc < 0)   isoc = 0;

	SOC = (unsigned char)isoc;
	last_dist_acr = real_acr;

	// Indicate a LAST_DIST_ACR updated is needed
	UpdateGaugeLDACR();
}

